#include "MQTT.hpp"
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>
#include <format>
#include "string_hash.hpp"

MQTT::MQTT() 
  : client(ioc)
{
}

void MQTT::Boot(const ConnectInf& inf)
{
  connInf=inf;
  netthread=std::jthread(&MQTT::WorkThread,this);
}

MQTT::~MQTT()
{
  if(netthread.joinable())
  {
    netthread.request_stop();
    netthread.join();
  }
}

boost::asio::awaitable<void> MQTT::channelDeviceConnect(const std::stop_token& token)
{
  auto [rc,props]=co_await client.async_publish<async_mqtt5::qos_e::at_least_once>(
      "/supermatrix/device/connect",
      [this]()->std::string{
        nlohmann::json j=nlohmann::json::object();
        j["DeviceID"]=connInf.clientID;
        j["Online"]=true;
        return j.dump();
      }(),
      async_mqtt5::retain_e::no,
      async_mqtt5::publish_props{},
      boost::asio::use_awaitable
  );
  if(rc)
    spdlog::warn("MQTT channelDeviceConnect LoginMsg failed : {}",rc.message());

  boost::asio::steady_timer timer(ioc);
  timer.expires_after(std::chrono::seconds(1));
  while(!token.stop_requested())
    co_await timer.async_wait(boost::asio::use_awaitable);
  runFlag=false;
  co_await client.async_disconnect(
    async_mqtt5::disconnect_rc_e::disconnect_with_will_message,
    async_mqtt5::disconnect_props {},
    boost::asio::use_awaitable
  );
  client.cancel();
  co_return;
}

boost::asio::awaitable<void> MQTT::channelCameraControl()
{
  std::string topic=std::format("/supermatrix/device/{}/camera/control",connInf.clientID);
  async_mqtt5::subscribe_topic sub_topic = {
    topic,
		async_mqtt5::subscribe_options {
			async_mqtt5::qos_e::exactly_once,
			async_mqtt5::no_local_e::yes,
			async_mqtt5::retain_as_published_e::dont,
			async_mqtt5::retain_handling_e::not_send
		}
	};
  auto [sub_codes,sub_props]=co_await client.async_subscribe(
    sub_topic,async_mqtt5::subscribe_props {},boost::asio::use_awaitable
  );
  if(sub_codes[0])
    throw std::runtime_error("MQTT channelCameraControl subscribe_topic failed");

  nlohmann::json result=nlohmann::json::object();
  while(runFlag)
  {
    try
    {
      auto [topic,payload,publish_props]=co_await client.async_receive(boost::asio::use_awaitable);
      nlohmann::json j=nlohmann::json::parse(payload);
      switch(detail::shash(j["Command"].get<std::string>()).value())
      {
        case "StartShot"_sh.value():
          funcAllStartShot();
          result["Value"]=nullptr;
        break;
        case "StopShot"_sh.value():
          funcAllStopShot();
          result["Value"]=nullptr;
        break;
        case "ShotStatus"_sh.value():
          result["Value"]=funcAllShotStatus();
        break;
        default:
          throw std::runtime_error(std::format("Unknow Command : {}",j["Command"].get<std::string>()));
      }
      result["Status"]=true;
      result["Cause"]=nullptr;
    }
    catch(const nlohmann::json::exception& e)
    {
      spdlog::warn("MQTT channelCameraControl Receive data not JSON : {}", e.what());
      continue;
    }
    catch(const async_mqtt5::error_code& e)
    {
      if(!runFlag) co_return;
      else spdlog::error("MQTT channelCameraControl SendData Failed : {}",e.what());
      continue;
    }
    catch(const std::runtime_error& e)
    {
      result["Status"]=false;
      result["Cause"]=e.what();
      result["Value"]=nullptr;
    }
    auto [rc,props] = co_await client.async_publish<async_mqtt5::qos_e::exactly_once>(
      topic,result.dump(),
      async_mqtt5::retain_e::no,
      async_mqtt5::publish_props {},
      boost::asio::use_awaitable
    );
    if(rc)
      spdlog::error("MQTT channelCameraControl returnMsg Failed");
  }
  co_return;
}

void MQTT::WorkThread(std::stop_token token)
{
  client
    .credentials(connInf.clientID,connInf.username,connInf.password)
    .brokers(connInf.ipaddr,connInf.port)
    .will(
      async_mqtt5::will(
        "/supermatrix/device/connect",
        [this]()->std::string{
          nlohmann::json j=nlohmann::json::object();
          j["DeviceID"]=connInf.clientID;
          j["Online"]=false;
          return j.dump();
        }(),
        async_mqtt5::qos_e::at_most_once
      )
    )
    .async_run(boost::asio::detached);
  co_spawn(ioc.get_executor(),channelDeviceConnect(token),boost::asio::detached);
  co_spawn(ioc.get_executor(),channelCameraControl(),boost::asio::detached);
  ioc.run();
}

